#include "HttpServer.h"

#include "HttpContext.h"
#include "HttpRequest.h"
#include "HttpResponse.h"
#include "../Buffer.h"

#include <memory>
#include <any>
#include <iostream>
#include "../util.h"

using namespace std::placeholders;

void defaultHttpCallback(const HttpRequest&, HttpResponse* resp)
{
    resp->setStatusCode(HttpResponse::k404NotFound);
    resp->setStatusMessage("Not Found");
    resp->setCloseConnection(true);
}

// HttpServer::HttpServer(EventLoop* loop, const std::string& name)
HttpServer::HttpServer(EventLoop* loop, int port)
    : server_(loop, port),
    httpCallback_(defaultHttpCallback)
{
    server_.setConnectionCallback(std::bind(&HttpServer::onConnection, this, _1));
    server_.setMessageCallback(std::bind(&HttpServer::onMessage, this, _1, _2));
}

HttpServer::~HttpServer()
{
}

void HttpServer::start()
{
    server_.start();
}

void HttpServer::onConnection(const TcpConnectionPtr& conn)
{
    conn->setContext(HttpContext());
}


// void HttpServer::onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp receiveTime)
void HttpServer::onMessage(const TcpConnectionPtr& conn, Buffer* buf)
{
    while ( buf->readable() ) {
        HttpContext* context = std::any_cast<HttpContext>(conn->getMutableContext());
        if (!parseRequest(buf, context))
        {
            conn->send("HTTP/1.1 400 Bad Request\r\n\r\n");
            conn->shutdown();
            break;
        }

        if (context->gotAll())
        {
            onRequest(conn, context->request());
            context->reset();
            break;
        }
    }
}

void HttpServer::onRequest(const TcpConnectionPtr& conn, const HttpRequest& req)
{
    const string& connection = req.getHeader("Connection");
    bool close = connection == "close" || (req.getVersion() == HttpRequest::kHttp10 && connection != "Keep-Alive");
    HttpResponse response(close);
    // HttpResponse response(false);
    httpCallback_(req, &response);
    Buffer buf;
    response.appendToBuffer(&buf);
    conn->send(&buf);
    if (response.closeConnection())
    {
        conn->shutdown();
    }
}